package org.infinispan.plugins.maven.defaults; import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId; import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration; import static org.twdata.maven.mojoexecutor.MojoExecutor.element; import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo; import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment; import static org.twdata.maven.mojoexecutor.MojoExecutor.goal; import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId; import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin; import static org.twdata.maven.mojoexecutor.MojoExecutor.version; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.BuildPluginManager; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; /** * A maven plugin to extract default values from various AtributeDefinitions, output them to a specified properties/asciidoc * file and process xsd files so that placeholders are replaced with the extracted defaults. * * @author Ryan Emerson */ @Mojo(name = "extract-defaults", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyResolution = ResolutionScope.RUNTIME) public class DefaultsExtractorMojo extends AbstractMojo { private enum AttributeDefType { ISPN, SERVER, ALL } @Parameter(required = true, defaultValue = "${project.build.directory}/defaults.properties") private String defaultsFile; @Parameter(defaultValue = "ISPN") private AttributeDefType attributeDefType; @Parameter(defaultValue = "false") private boolean outputAscii; @Parameter(defaultValue = "true") private boolean filterXsd; @Parameter(defaultValue = "${project.basedir}/src/main/resources/schema") private String xsdSrcPath; @Parameter(defaultValue = "${project.build.directory}/classes/schema") private String xsdTargetPath; @Parameter private List<String> jars = new ArrayList<>(); @Parameter(defaultValue = "${project}") private MavenProject mavenProject; @Parameter(defaultValue = "${session}") private MavenSession mavenSession; @Component private BuildPluginManager pluginManager; private DefaultsResolver ispnResovler = new InfinispanDefaultsResolver(); private DefaultsResolver serverResovler = new ServerResourceDefaultsResolver(); private List<String> classPaths; private ClassLoader classLoader; public void execute() throws MojoExecutionException { try { this.classPaths = mavenProject.getCompileClasspathElements(); URL[] classLoaderUrls = classPaths.stream() .map(strPath -> FileSystems.getDefault().getPath(strPath)) .map(this::pathToUrl) .toArray(URL[]::new); this.classLoader = new URLClassLoader(classLoaderUrls, Thread.currentThread().getContextClassLoader()); } catch (DependencyResolutionRequiredException e) { throw new MojoExecutionException("Exception encountered during class extraction", e); } Set<Class> configClasses = new HashSet<>(); getClassesFromClasspath(configClasses); jars.forEach(jar -> getClassesFromJar(jar, configClasses)); Map<String, String> defaults = extractDefaults(configClasses); writeDefaultsToFile(defaults); if (filterXsd) filterXsdSchemas(); } private boolean isValidClass(String className) { return ispnResovler.isValidClass(className) || serverResovler.isValidClass(className); } private Map<String, String> extractDefaults(Set<Class> classes) { String separator = outputAscii ? "-" : "."; Map<String, String> defaults = new HashMap<>(); boolean extractAll = attributeDefType == AttributeDefType.ALL; if (extractAll || attributeDefType == AttributeDefType.ISPN) defaults.putAll(ispnResovler.extractDefaults(classes, separator)); if (extractAll || attributeDefType == AttributeDefType.SERVER) defaults.putAll(serverResovler.extractDefaults(classes, separator)); return defaults; } private URL pathToUrl(Path path) { try { return path.toUri().toURL(); } catch (MalformedURLException ignore) { return null; } } private void getClassesFromClasspath(Set<Class> classes) throws MojoExecutionException { try { FileSystem fs = FileSystems.getDefault(); PathMatcher classDir = fs.getPathMatcher("glob:*/**/target/classes"); List<File> packageRoots = classPaths.stream() .map(fs::getPath) .filter(classDir::matches) .map(Path::toFile) .collect(Collectors.toList()); for (File packageRoot : packageRoots) getClassesInPackage(packageRoot, "", classes); } catch (ClassNotFoundException e) { throw new MojoExecutionException("Exception encountered during class extraction", e); } } private void getClassesInPackage(File packageDir, String packageName, Set<Class> classes) throws ClassNotFoundException { if (packageDir.exists()) { for (File file : packageDir.listFiles()) { String fileName = file.getName(); if (file.isDirectory()) { String subpackage = packageName.isEmpty() ? fileName : packageName + "." + fileName; getClassesInPackage(file, subpackage, classes); } else if (isValidClass(fileName)) { String className = fileName.substring(0, fileName.length() - 6); classes.add(Class.forName(packageName + "." + className, true, classLoader)); } } } } private void getClassesFromJar(String jarName, Set<Class> classes) { // Ignore version number, necessary as jar is loaded differently when sub module is installed in isolation Optional<String> jarPath = classPaths.stream().filter(str -> str.contains(jarName)).findFirst(); if (jarPath.isPresent()) { try { ZipInputStream jar = new ZipInputStream(new FileInputStream(jarPath.get())); for (ZipEntry entry = jar.getNextEntry(); entry != null; entry = jar.getNextEntry()) { if (!entry.isDirectory() && isValidClass(entry.getName())) { String className = entry.getName().replace("/", "."); classes.add(Class.forName(className.substring(0, className.length() - 6), true, classLoader)); } } } catch (IOException | ClassNotFoundException e) { getLog().error(String.format("Unable to process jar '%s'", jarName), e); } } else { // We just warn here, as jars are required for `mvn install`, but not for `mvn test` getLog().info("Skipping Jar '" + jarName + "' as it cannot be found on the classpath"); } } private void writeDefaultsToFile(Map<String, String> defaults) throws MojoExecutionException { File file = new File(defaultsFile); if (file.getParentFile() != null) file.getParentFile().mkdirs(); try (PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) { defaults.entrySet().stream() .map(this::formatOutput) .sorted() .forEach(printWriter::println); printWriter.flush(); } catch (IOException e) { throw new MojoExecutionException(String.format("Unable to write extracted defaults to the path '%s'", defaultsFile), e); } } private String formatOutput(Map.Entry<String, String> entry) { if (outputAscii) { return ":" + entry.getKey() + ": " + entry.getValue(); } return entry.getKey() + " = " + entry.getValue(); } private void filterXsdSchemas() throws MojoExecutionException { executeMojo( plugin( groupId("org.apache.maven.plugins"), artifactId("maven-resources-plugin"), version("2.6") ), goal("copy-resources"), configuration( element("overwrite", "true"), element("outputDirectory", defaultsFile), element("resources", element("resource", element("directory", xsdSrcPath), element("targetPath", xsdTargetPath), element("includes", element("include", "*.xsd") ), element("filtering", "true") ) ), element("filters", element("filter", defaultsFile) ) ), executionEnvironment( mavenProject, mavenSession, pluginManager ) ); } }